In this example we will use the IPUMS USA data to produce survey-based estimates for various geographic levels present in the IPUMS. This example uses the 2014-2018 ACS 5-year microdata.
The good folks at IPUMS have created a library to read in their data from the .gz file that you download. Be sure you right click and save the DDI codebook when you create your extract

This will save the xml file that contains all the information on the data (what is contained in the data file) to your computer. When using IPUMS, it will have a name like usa_xxxxx.xml where the x’s represent the extract number (I’m on 92 as of today).
You will also need to download the data file, by right clicking the Download.DAT link in the above image. This will save a .gz file to your computer, again with a name like: usa_xxxxx.dat.gz. Make sure this file and the xml file from above are in the same folder, preferably your class folder.
Be sure the ipumsr package is installed.
library(ipumsr)
ddi <- read_ipums_ddi("~/OneDrive - University of Texas at San Antonio/classes//gis_classwork/usa_00083.xml")
data <- read_ipums_micro(ddi)
## Use of data from IPUMS-USA is subject to conditions including that users should
## cite the data appropriately. Use command `ipums_conditions()` for more details.
data<-haven::zap_labels(data) #necessary to avoid problems with "labeled" data
Load some other packages
library(survey)
library(dplyr)
library(car)
library(ggplot2)
library(tigris)
library(classInt)
library(tmap)
Download geographic data for Public Use Microdata Areas
options(tigris_class = "sf")
pumas<-pumas(state = "TX", year = 2018, cb = T)
plot(pumas["GEOID10"], main = "Public Use Microdata Areas in Texas")

names(data)<-tolower(names(data))
Prepare variables
Here I recode several demographic variables
#weight variables, these have implied decimal places so we have to divde by 100, following the codebook
data$pwt <- data$perwt/100
data$hwt <- data$hhwt/100
#race/ethnicity
data$hisp <- Recode(data$hispan, recodes = "9=NA; 1:4='Hispanic'; 0='NonHispanic'")
data$race_rec <- Recode(data$race, recodes = "1='White'; 2='Black'; 3='Other'; 4:6='Asian'; 7:9='Other'")
data$race_eth <- interaction(data$hisp, data$race_rec, sep = "_")
data$race_eth <- as.factor(ifelse(substr(as.character(data$race_eth),1,8) == "Hispanic", "Hispanic", as.character(data$race_eth)))
data$race_eth <- relevel(data$race_eth, ref = "NonHispanic_White")
#sex
data$male <- ifelse(data$sex == 1,1,0)
#education
data$educ_level<- Recode(data$educd, recodes = "2:61='0LT_HS';62:64='1_HSD/GED';65:80='2_somecoll';90:100='2_somecoll'; 81:83='3_AssocDegree';101='4_bachelordegree'; 110:116='4_BAplus_GradDegree'; else=NA")
#employment
data$employed <- Recode(data$wrklstwk, recodes = "1=0;2=1; else=NA")
#citizenship
data$cit<-Recode(data$citizen, recodes = "1='US born'; 2='naturalized'; 3:4='notcitizen';else=NA ")
#industry
data$ind_group<-Recode(data$ind, recodes = "170:490='ag_extract'; 770='construction'; 1070:3990='manufac'; 4070:5790='whole_retail'; 6070:6390='trans'; 6470:6780='information'; 6870:7190= 'fire'; 7270=7790='prof/sci/manage'; 7860:8470='edu/social'; 8560:8690='arts'; 8770:9290='other'; 9370:9590='public_adm'; 9670:9870='military'; else=NA ")
data$proftech <- Recode(data$ind, recodes = "7270:7490=1; 0=NA; else=0")
#age in 10 year intervals
data$agecat<-cut(data$age, breaks = c(0, 18, 20, 30, 40, 50, 65, 120), include.lowest = T)
data$income <- ifelse(data$incwage>=999998, NA, data$incwage)
Generate survey design object
Here we identify the person weights and the survey design variables.
des<-svydesign(ids = ~cluster,
strata = ~ strata,
weights = ~pwt,
data = data)
join to geography
pumas$puma<-as.numeric(pumas$PUMACE10)
geo1<-geo_join(pumas, puma_est_employ, by_sp="puma",by_df= "puma")
## Warning: `group_by_()` was deprecated in dplyr 0.7.0.
## Please use `group_by()` instead.
## See vignette('programming') for more help
head(geo1)
geo2<-geo_join(pumas, puma_est_industry, by_sp="puma",by_df= "puma")
head(geo2)
geo3<-geo_join(pumas, gini.puma, by_sp="puma",by_df= "puma")
head(geo2)
Map estimates
Employment rates by PUMA
tmap_mode("plot")
## tmap mode set to plotting
tm_basemap("OpenStreetMap.Mapnik")+
tm_shape(geo1)+
tm_polygons("employed",
style="kmeans",
n=8,
legend.hist = TRUE) +
tm_layout(legend.outside = TRUE,
title = "Employment rate in Texas PUMAs \n 2014-2018")
## Linking to GEOS 3.9.0, GDAL 3.2.1, PROJ 7.2.1

## [1] "#FFFDDB" "#FEF1AF" "#FED97B" "#FEB441" "#F68820" "#DB5D0A" "#AF3E03"
## [8] "#7A2A05"
#tmap_mode("plot")
tmap_leaflet(tm_basemap("OpenStreetMap.Mapnik")+
tm_shape(geo2)+
tm_polygons("proftech",
style="kmeans",
n=8,
legend.hist = TRUE) +
tm_layout(legend.outside = TRUE,
title = "Employment rate in Texas PUMAs \n 2014-2018") )
tmap_mode("plot")
## tmap mode set to plotting
tmap_leaflet(tm_basemap("OpenStreetMap.Mapnik")+
tm_shape(geo3)+
tm_polygons("ineq",
style="kmeans",
n=8,
legend.hist = TRUE) +
tm_layout(legend.outside = TRUE,
title = "Employment rate in Texas PUMAs \n 2014-2018") )
Estimation for metro areas
Here we use core based statistical areas instead of PUMAs
mets<-core_based_statistical_areas(cb = T, year = 2018)
mets<-mets[grep(mets$NAME,pattern = "TX"),]
plot(mets["NAME"])

sts<-states(cb=T, year=2018)
sts<-sts%>%
filter(GEOID==48)
estimates by metro area
met_est_edu<-svyby(formula = ~educ_level,
by = ~met2013,
design=subset(des,age>25),
FUN=svymean,
na.rm=T )
met_est_employ<-svyby(formula = ~employed,
by = ~met2013,
design=subset(des, age%in%18:65),
FUN=svymean,
na.rm=T )
met_est_industry<-svyby(formula = ~proftech,
by = ~met2013,
design=subset(des, employed==1),
FUN=svymean,
na.rm=T )
head(met_est_edu)
head(met_est_employ)
head(met_est_industry)
mets$met2013<-as.numeric(mets$GEOID)
geo3<-geo_join(mets, met_est_employ,by_sp= "met2013",by_df= "met2013")
Note, grey Metros are ones that are not identified in the ACS
tmap_mode("plot")
## tmap mode set to plotting
tmap_leaflet(tm_basemap("OpenStreetMap.Mapnik")+
tm_shape(geo3)+
tm_polygons("employed",
style="kmeans",
n=8,
legend.hist = TRUE) +
tm_layout(legend.outside = TRUE,
title = "Employment rate in Texas Metro Areas \n 2014-2018") )
Estimation for Counties
cos<-counties(cb= T,state = "TX", year = 2018)
plot(cos["NAME"])

sts<-states(cb=T, year=2018)
sts<-sts%>%
filter(GEOID==48)
estimates by county area
cos_est_edu<-svyby(formula = ~educ_level,
by = ~countyfip,
design=subset(des,age>25),
FUN=svymean, na.rm=T )
cos_est_employ<-svyby(formula = ~employed,
by = ~countyfip,
design=subset(des, age%in%18:65),
FUN=svymean, na.rm=T )
cos_est_industry<-svyby(formula = ~proftech,
by = ~countyfip,
design=subset(des, employed==1),
FUN=svymean, na.rm=T )
head(cos_est_edu)
head(cos_est_employ)
head(cos_est_industry)
Again, the ACS doesn’t identify counties in the microdata except for those counties with small populations. The list of identified counties can be found here
cos$cofip<-as.numeric(cos$COUNTYFP)
geo4<-geo_join(cos, cos_est_employ,by_sp= "cofip",by_df= "countyfip")
tmap_mode("plot")
## tmap mode set to plotting
tmap_leaflet(
tm_basemap("OpenStreetMap.Mapnik")+
tm_shape(geo4)+
tm_polygons("employed",
style="kmeans",
n=8,
legend.hist = TRUE) +
tm_layout(legend.outside = TRUE,
title = "Employment rate in Texas Counties \n 2014-2018") )
LS0tDQp0aXRsZTogIlVzaW5nIElQVU1TIFVTQSBmb3IgRXN0aW1hdGlvbiBvZiBQb3B1bGF0aW9uIENoYXJhY3RlcmlzdGljcyBpbiBWYXJpb3VzIEdlb2dyYXBoaWMgQXJlYXMiDQphdXRob3I6ICJDb3JleSBTLiBTcGFya3MsIFBoLkQuIC0gVW5pdmVyc2l0eSBvZiBUZXhhcyBhdCBTYW4gQW50b25pbyINCmRhdGU6ICJgciBmb3JtYXQoU3lzLnRpbWUoKSwgJyVkICVCLCAlWScpYCINCm91dHB1dDoNCiAgIGh0bWxfZG9jdW1lbnQ6DQogICAgZGZfcHJpbnQ6IHBhZ2VkDQogICAgZmlnX2hlaWdodDogNw0KICAgIGZpZ193aWR0aDogNw0KICAgIHRvYzogeWVzDQogICAgdG9jX2Zsb2F0OiB5ZXMNCiAgICBjb2RlX2Rvd25sb2FkOiB0cnVlDQotLS0NCg0KSW4gdGhpcyBleGFtcGxlIHdlIHdpbGwgdXNlIHRoZSBbSVBVTVMgVVNBXShodHRwczovL3VzYS5pcHVtcy5vcmcvdXNhLykgZGF0YSB0byBwcm9kdWNlIHN1cnZleS1iYXNlZCBlc3RpbWF0ZXMgZm9yIHZhcmlvdXMgZ2VvZ3JhcGhpYyBsZXZlbHMgcHJlc2VudCBpbiB0aGUgSVBVTVMuIFRoaXMgZXhhbXBsZSB1c2VzIHRoZSAyMDE0LTIwMTggQUNTIDUteWVhciBtaWNyb2RhdGEuIA0KDQoNCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFfQ0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGVjaG8gPSBUUlVFKQ0KYGBgDQoNClRoZSBnb29kIGZvbGtzIGF0IElQVU1TIGhhdmUgY3JlYXRlZCBhIGxpYnJhcnkgdG8gcmVhZCBpbiB0aGVpciBkYXRhIGZyb20gdGhlIC5neiBmaWxlIHRoYXQgeW91IGRvd25sb2FkLiBCZSBzdXJlIHlvdSByaWdodCBjbGljayBhbmQgc2F2ZSB0aGUgRERJIGNvZGVib29rIHdoZW4geW91IGNyZWF0ZSB5b3VyIGV4dHJhY3QNCg0KIVtdKC4uL2ltYWdlcy9pbXB1bTEucG5nKQ0KDQpUaGlzIHdpbGwgc2F2ZSB0aGUgeG1sIGZpbGUgdGhhdCBjb250YWlucyBhbGwgdGhlIGluZm9ybWF0aW9uIG9uIHRoZSBkYXRhICh3aGF0IGlzIGNvbnRhaW5lZCBpbiB0aGUgZGF0YSBmaWxlKSB0byB5b3VyIGNvbXB1dGVyLiBXaGVuIHVzaW5nIElQVU1TLCBpdCB3aWxsIGhhdmUgYSBuYW1lIGxpa2UgYHVzYV94eHh4eC54bWxgIHdoZXJlIHRoZSB4J3MgcmVwcmVzZW50IHRoZSBleHRyYWN0IG51bWJlciAoSSdtIG9uIDkyIGFzIG9mIHRvZGF5KS4gDQoNCllvdSB3aWxsIGFsc28gbmVlZCB0byBkb3dubG9hZCB0aGUgZGF0YSBmaWxlLCBieSByaWdodCBjbGlja2luZyB0aGUgKipEb3dubG9hZC5EQVQqKiBsaW5rIGluIHRoZSBhYm92ZSBpbWFnZS4gVGhpcyB3aWxsIHNhdmUgYSAuZ3ogZmlsZSB0byB5b3VyIGNvbXB1dGVyLCBhZ2FpbiB3aXRoIGEgbmFtZSBsaWtlOiBgdXNhX3h4eHh4LmRhdC5nemAuIE1ha2Ugc3VyZSB0aGlzIGZpbGUgYW5kIHRoZSB4bWwgZmlsZSBmcm9tIGFib3ZlIGFyZSBpbiB0aGUgc2FtZSBmb2xkZXIsIHByZWZlcmFibHkgeW91ciBjbGFzcyBmb2xkZXIuIA0KDQpCZSBzdXJlIHRoZSBgaXB1bXNyYCBwYWNrYWdlIGlzIGluc3RhbGxlZC4gDQoNCmBgYHtyfQ0KbGlicmFyeShpcHVtc3IpDQpkZGkgPC0gcmVhZF9pcHVtc19kZGkoIn4vT25lRHJpdmUgLSBVbml2ZXJzaXR5IG9mIFRleGFzIGF0IFNhbiBBbnRvbmlvL2NsYXNzZXMvL2dpc19jbGFzc3dvcmsvdXNhXzAwMDgzLnhtbCIpDQpkYXRhIDwtIHJlYWRfaXB1bXNfbWljcm8oZGRpKQ0KZGF0YTwtaGF2ZW46OnphcF9sYWJlbHMoZGF0YSkgI25lY2Vzc2FyeSB0byBhdm9pZCBwcm9ibGVtcyB3aXRoICJsYWJlbGVkIiBkYXRhDQoNCmBgYA0KDQojIyBMb2FkIHNvbWUgb3RoZXIgcGFja2FnZXMNCmBgYHtyLCBtZXNzYWdlPUZBTFNFfQ0KbGlicmFyeShzdXJ2ZXkpDQpsaWJyYXJ5KGRwbHlyKQ0KbGlicmFyeShjYXIpDQpsaWJyYXJ5KGdncGxvdDIpDQpsaWJyYXJ5KHRpZ3JpcykNCmxpYnJhcnkoY2xhc3NJbnQpDQpsaWJyYXJ5KHRtYXApDQoNCmBgYA0KDQoNCiMjIyBEb3dubG9hZCBnZW9ncmFwaGljIGRhdGEgZm9yIFB1YmxpYyBVc2UgTWljcm9kYXRhIEFyZWFzDQpgYGB7ciwgcmVzdWx0cz0naGlkZSd9DQpvcHRpb25zKHRpZ3Jpc19jbGFzcyA9ICJzZiIpDQpwdW1hczwtcHVtYXMoc3RhdGUgPSAiVFgiLCB5ZWFyID0gMjAxOCwgY2IgPSBUKQ0KcGxvdChwdW1hc1siR0VPSUQxMCJdLCBtYWluID0gIlB1YmxpYyBVc2UgTWljcm9kYXRhIEFyZWFzIGluIFRleGFzIikNCmBgYA0KDQpgYGB7cn0NCm5hbWVzKGRhdGEpPC10b2xvd2VyKG5hbWVzKGRhdGEpKQ0KYGBgDQoNCiMjIFByZXBhcmUgdmFyaWFibGVzDQpIZXJlIEkgcmVjb2RlIHNldmVyYWwgZGVtb2dyYXBoaWMgdmFyaWFibGVzDQoNCmBgYHtyfQ0KI3dlaWdodCB2YXJpYWJsZXMsIHRoZXNlIGhhdmUgaW1wbGllZCBkZWNpbWFsIHBsYWNlcyBzbyB3ZSBoYXZlIHRvIGRpdmRlIGJ5IDEwMCwgZm9sbG93aW5nIHRoZSBjb2RlYm9vaw0KZGF0YSRwd3QgPC0gZGF0YSRwZXJ3dC8xMDANCmRhdGEkaHd0IDwtIGRhdGEkaGh3dC8xMDANCg0KI3JhY2UvZXRobmljaXR5DQpkYXRhJGhpc3AgPC0gUmVjb2RlKGRhdGEkaGlzcGFuLCByZWNvZGVzID0gIjk9TkE7IDE6ND0nSGlzcGFuaWMnOyAwPSdOb25IaXNwYW5pYyciKQ0KZGF0YSRyYWNlX3JlYyA8LSBSZWNvZGUoZGF0YSRyYWNlLCByZWNvZGVzID0gIjE9J1doaXRlJzsgMj0nQmxhY2snOyAzPSdPdGhlcic7IDQ6Nj0nQXNpYW4nOyA3Ojk9J090aGVyJyIpDQpkYXRhJHJhY2VfZXRoIDwtIGludGVyYWN0aW9uKGRhdGEkaGlzcCwgZGF0YSRyYWNlX3JlYywgc2VwID0gIl8iKQ0KZGF0YSRyYWNlX2V0aCAgPC0gYXMuZmFjdG9yKGlmZWxzZShzdWJzdHIoYXMuY2hhcmFjdGVyKGRhdGEkcmFjZV9ldGgpLDEsOCkgPT0gIkhpc3BhbmljIiwgIkhpc3BhbmljIiwgYXMuY2hhcmFjdGVyKGRhdGEkcmFjZV9ldGgpKSkNCmRhdGEkcmFjZV9ldGggPC0gcmVsZXZlbChkYXRhJHJhY2VfZXRoLCByZWYgPSAiTm9uSGlzcGFuaWNfV2hpdGUiKQ0KDQojc2V4DQpkYXRhJG1hbGUgPC0gaWZlbHNlKGRhdGEkc2V4ID09IDEsMSwwKQ0KDQojZWR1Y2F0aW9uDQpkYXRhJGVkdWNfbGV2ZWw8LSBSZWNvZGUoZGF0YSRlZHVjZCwgcmVjb2RlcyA9ICIyOjYxPScwTFRfSFMnOzYyOjY0PScxX0hTRC9HRUQnOzY1OjgwPScyX3NvbWVjb2xsJzs5MDoxMDA9JzJfc29tZWNvbGwnOyA4MTo4Mz0nM19Bc3NvY0RlZ3JlZSc7MTAxPSc0X2JhY2hlbG9yZGVncmVlJzsgMTEwOjExNj0nNF9CQXBsdXNfR3JhZERlZ3JlZSc7IGVsc2U9TkEiKQ0KDQojZW1wbG95bWVudA0KZGF0YSRlbXBsb3llZCA8LSBSZWNvZGUoZGF0YSR3cmtsc3R3aywgcmVjb2RlcyA9ICIxPTA7Mj0xOyBlbHNlPU5BIikNCg0KI2NpdGl6ZW5zaGlwDQpkYXRhJGNpdDwtUmVjb2RlKGRhdGEkY2l0aXplbiwgcmVjb2RlcyA9ICIxPSdVUyBib3JuJzsgMj0nbmF0dXJhbGl6ZWQnOyAzOjQ9J25vdGNpdGl6ZW4nO2Vsc2U9TkEgIikNCg0KI2luZHVzdHJ5DQpkYXRhJGluZF9ncm91cDwtUmVjb2RlKGRhdGEkaW5kLCByZWNvZGVzID0gIjE3MDo0OTA9J2FnX2V4dHJhY3QnOyA3NzA9J2NvbnN0cnVjdGlvbic7IDEwNzA6Mzk5MD0nbWFudWZhYyc7IDQwNzA6NTc5MD0nd2hvbGVfcmV0YWlsJzsgNjA3MDo2MzkwPSd0cmFucyc7IDY0NzA6Njc4MD0naW5mb3JtYXRpb24nOyA2ODcwOjcxOTA9ICdmaXJlJzsgNzI3MD03NzkwPSdwcm9mL3NjaS9tYW5hZ2UnOyA3ODYwOjg0NzA9J2VkdS9zb2NpYWwnOyA4NTYwOjg2OTA9J2FydHMnOyA4NzcwOjkyOTA9J290aGVyJzsgOTM3MDo5NTkwPSdwdWJsaWNfYWRtJzsgOTY3MDo5ODcwPSdtaWxpdGFyeSc7IGVsc2U9TkEgIikNCg0KZGF0YSRwcm9mdGVjaCA8LSBSZWNvZGUoZGF0YSRpbmQsIHJlY29kZXMgPSAiNzI3MDo3NDkwPTE7IDA9TkE7IGVsc2U9MCIpDQoNCiNhZ2UgaW4gMTAgeWVhciBpbnRlcnZhbHMNCmRhdGEkYWdlY2F0PC1jdXQoZGF0YSRhZ2UsIGJyZWFrcyA9IGMoMCwgMTgsIDIwLCAzMCwgNDAsIDUwLCA2NSwgMTIwKSwgaW5jbHVkZS5sb3dlc3QgPSBUKQ0KDQpkYXRhJGluY29tZSA8LSBpZmVsc2UoZGF0YSRpbmN3YWdlPj05OTk5OTgsIE5BLCBkYXRhJGluY3dhZ2UpDQpgYGANCg0KDQoNCiMjIEdlbmVyYXRlIHN1cnZleSBkZXNpZ24gb2JqZWN0DQpIZXJlIHdlIGlkZW50aWZ5IHRoZSBwZXJzb24gd2VpZ2h0cyBhbmQgdGhlIHN1cnZleSBkZXNpZ24gdmFyaWFibGVzLg0KDQpgYGB7cn0NCmRlczwtc3Z5ZGVzaWduKGlkcyA9IH5jbHVzdGVyLA0KICAgICAgICAgICAgICAgc3RyYXRhID0gfiBzdHJhdGEsDQogICAgICAgICAgICAgICB3ZWlnaHRzID0gfnB3dCwNCiAgICAgICAgICAgICAgIGRhdGEgPSBkYXRhKQ0KYGBgDQoNCiMjIHBlcmZvcm0gc3VydmV5IGVzdGltYXRpb24gZm9yIFBVTUFzDQpUaGUgYHN2eWJ5KClgIGZ1bmN0aW9uIGFsbG93cyB1cyBjYWxjdWxhdGUgZXN0aW1hdGVzIGZvciBkaWZmZXJlbnQgKipzdWItZG9tYWlucyoqIHdpdGhpbiB0aGUgZGF0YSwgdGhpcyBjb3VsZCBiZSBhIGRlbW9ncmFwaGljIGNoYXJhY3RlcmlzdGljLCBidXQgd2UnbGwgdXNlIG91ciBnZW9ncmFwaGljIGxldmVsLiANCg0KYGBge3J9DQp0ZXN0PC1zdnl0YWJsZSh+SShjaXQ9PSJVUyBib3JuIikrcHVtYStzZXgsIGRlc2lnbj1kZXMgKQ0KDQpwdW1hX2VzdF9lZHU8LXN2eWJ5KGZvcm11bGEgPSB+ZWR1Y19sZXZlbCwNCiAgICAgICAgICAgICAgICAgICAgYnkgPSB+cHVtYSwNCiAgICAgICAgICAgICAgICAgICAgZGVzaWduID0gc3Vic2V0KGRlcywgYWdlPjI1KSwNCiAgICAgICAgICAgICAgICAgICAgRlVOPXN2eW1lYW4sDQogICAgICAgICAgICAgICAgICAgIG5hLnJtID0gVFJVRSApDQoNCnB1bWFfZXN0X2VtcGxveTwtc3Z5YnkoZm9ybXVsYSA9IH5lbXBsb3llZCwNCiAgICAgICAgICAgICAgICAgICAgICAgYnkgPSB+cHVtYSwNCiAgICAgICAgICAgICAgICAgICAgICAgZGVzaWduPXN1YnNldChkZXMsIGFnZSAlaW4lIDE4OjY1KSwNCiAgICAgICAgICAgICAgICAgICAgICAgRlVOPXN2eW1lYW4sDQogICAgICAgICAgICAgICAgICAgICAgIG5hLnJtID0gVFJVRSApDQoNCnB1bWFfZXN0X2luZHVzdHJ5PC1zdnlieShmb3JtdWxhID0gfnByb2Z0ZWNoLA0KICAgICAgICAgICAgICAgICAgICAgICAgIGJ5ID0gfnB1bWEsDQogICAgICAgICAgICAgICAgICAgICAgICAgZGVzaWduID0gc3Vic2V0KGRlcywgZW1wbG95ZWQ9PTEpLA0KICAgICAgICAgICAgICAgICAgICAgICAgIEZVTiA9IHN2eW1lYW4sDQogICAgICAgICAgICAgICAgICAgICAgICAgbmEucm0gPSBUUlVFICkNCg0KbGlicmFyeShyZWxkaXN0KQ0KZ2luaS5wdW1hPC1kYXRhJT4lDQogIGZpbHRlcihpbmNvbWUgPjAsIGlzLm5hKGluY29tZSk9PUYpJT4lDQogIGdyb3VwX2J5KCBwdW1hKSU+JQ0KICBzdW1tYXJpemUoaW5lcSA9IGdpbmkoaW5jb21lLCB3ZWlnaHRzID0gcHd0KSklPiUNCiAgdW5ncm91cCgpDQoNCg0KaGVhZChwdW1hX2VzdF9lZHUpDQpoZWFkKHB1bWFfZXN0X2VtcGxveSkNCmhlYWQocHVtYV9lc3RfaW5kdXN0cnkpDQpgYGANCg0KIyMgam9pbiB0byBnZW9ncmFwaHkNCmBgYHtyfQ0KcHVtYXMkcHVtYTwtYXMubnVtZXJpYyhwdW1hcyRQVU1BQ0UxMCkNCg0KZ2VvMTwtZ2VvX2pvaW4ocHVtYXMsIHB1bWFfZXN0X2VtcGxveSwgYnlfc3A9InB1bWEiLGJ5X2RmPSAicHVtYSIpDQpoZWFkKGdlbzEpDQoNCmdlbzI8LWdlb19qb2luKHB1bWFzLCBwdW1hX2VzdF9pbmR1c3RyeSwgYnlfc3A9InB1bWEiLGJ5X2RmPSAicHVtYSIpDQpoZWFkKGdlbzIpDQoNCmdlbzM8LWdlb19qb2luKHB1bWFzLCBnaW5pLnB1bWEsIGJ5X3NwPSJwdW1hIixieV9kZj0gInB1bWEiKQ0KaGVhZChnZW8yKQ0KDQpgYGANCg0KIyMgTWFwIGVzdGltYXRlcw0KDQojIyMgRW1wbG95bWVudCByYXRlcyBieSBQVU1BDQpgYGB7cn0NCg0KdG1hcF9tb2RlKCJwbG90IikNCg0KdG1fYmFzZW1hcCgiT3BlblN0cmVldE1hcC5NYXBuaWsiKSsNCiAgdG1fc2hhcGUoZ2VvMSkrDQogIHRtX3BvbHlnb25zKCJlbXBsb3llZCIsDQogICAgICAgICAgICAgIHN0eWxlPSJrbWVhbnMiLA0KICAgICAgICAgICAgICBuPTgsDQogICAgICAgICAgICAgIGxlZ2VuZC5oaXN0ID0gVFJVRSkgKw0KICB0bV9sYXlvdXQobGVnZW5kLm91dHNpZGUgPSBUUlVFLA0KICAgICAgICAgICAgdGl0bGUgPSAiRW1wbG95bWVudCByYXRlIGluIFRleGFzIFBVTUFzIFxuIDIwMTQtMjAxOCIpIA0KDQoNCmBgYA0KDQpgYGB7cn0NCiN0bWFwX21vZGUoInBsb3QiKQ0KdG1hcF9sZWFmbGV0KHRtX2Jhc2VtYXAoIk9wZW5TdHJlZXRNYXAuTWFwbmlrIikrDQogIHRtX3NoYXBlKGdlbzIpKw0KICB0bV9wb2x5Z29ucygicHJvZnRlY2giLA0KICAgICAgICAgICAgICBzdHlsZT0ia21lYW5zIiwNCiAgICAgICAgICAgICAgbj04LA0KICAgICAgICAgICAgICBsZWdlbmQuaGlzdCA9IFRSVUUpICsNCiB0bV9sYXlvdXQobGVnZW5kLm91dHNpZGUgPSBUUlVFLA0KICAgICAgICAgICAgdGl0bGUgPSAiRW1wbG95bWVudCByYXRlIGluIFRleGFzIFBVTUFzIFxuIDIwMTQtMjAxOCIpICApDQoNCmBgYA0KDQpgYGB7cn0NCnRtYXBfbW9kZSgicGxvdCIpDQp0bWFwX2xlYWZsZXQodG1fYmFzZW1hcCgiT3BlblN0cmVldE1hcC5NYXBuaWsiKSsNCiAgdG1fc2hhcGUoZ2VvMykrDQogIHRtX3BvbHlnb25zKCJpbmVxIiwNCiAgICAgICAgICAgICAgc3R5bGU9ImttZWFucyIsDQogICAgICAgICAgICAgIG49OCwNCiAgICAgICAgICAgICAgbGVnZW5kLmhpc3QgPSBUUlVFKSArDQogdG1fbGF5b3V0KGxlZ2VuZC5vdXRzaWRlID0gVFJVRSwNCiAgICAgICAgICAgIHRpdGxlID0gIkVtcGxveW1lbnQgcmF0ZSBpbiBUZXhhcyBQVU1BcyBcbiAyMDE0LTIwMTgiKSAgKQ0KDQpgYGANCg0KDQojIyBFc3RpbWF0aW9uIGZvciBtZXRybyBhcmVhcw0KSGVyZSB3ZSB1c2UgY29yZSBiYXNlZCBzdGF0aXN0aWNhbCBhcmVhcyBpbnN0ZWFkIG9mIFBVTUFzDQoNCmBgYHtyLCByZXN1bHRzPSdoaWRlJ30NCm1ldHM8LWNvcmVfYmFzZWRfc3RhdGlzdGljYWxfYXJlYXMoY2IgPSBULCB5ZWFyID0gMjAxOCkNCm1ldHM8LW1ldHNbZ3JlcChtZXRzJE5BTUUscGF0dGVybiA9ICAiVFgiKSxdDQpwbG90KG1ldHNbIk5BTUUiXSkNCg0Kc3RzPC1zdGF0ZXMoY2I9VCwgeWVhcj0yMDE4KQ0Kc3RzPC1zdHMlPiUNCiAgZmlsdGVyKEdFT0lEPT00OCkNCmBgYA0KIyMgZXN0aW1hdGVzIGJ5IG1ldHJvIGFyZWENCmBgYHtyfQ0KbWV0X2VzdF9lZHU8LXN2eWJ5KGZvcm11bGEgPSB+ZWR1Y19sZXZlbCwNCiAgICAgICAgICAgICAgICAgICBieSA9IH5tZXQyMDEzLA0KICAgICAgICAgICAgICAgICAgIGRlc2lnbj1zdWJzZXQoZGVzLGFnZT4yNSksDQogICAgICAgICAgICAgICAgICAgRlVOPXN2eW1lYW4sDQogICAgICAgICAgICAgICAgICAgbmEucm09VCApDQoNCm1ldF9lc3RfZW1wbG95PC1zdnlieShmb3JtdWxhID0gfmVtcGxveWVkLA0KICAgICAgICAgICAgICAgICAgICAgIGJ5ID0gfm1ldDIwMTMsDQogICAgICAgICAgICAgICAgICAgICAgZGVzaWduPXN1YnNldChkZXMsIGFnZSVpbiUxODo2NSksDQogICAgICAgICAgICAgICAgICAgICAgRlVOPXN2eW1lYW4sDQogICAgICAgICAgICAgICAgICAgICAgbmEucm09VCApDQoNCm1ldF9lc3RfaW5kdXN0cnk8LXN2eWJ5KGZvcm11bGEgPSB+cHJvZnRlY2gsDQogICAgICAgICAgICAgICAgICAgICAgICBieSA9IH5tZXQyMDEzLA0KICAgICAgICAgICAgICAgICAgICAgICAgZGVzaWduPXN1YnNldChkZXMsIGVtcGxveWVkPT0xKSwNCiAgICAgICAgICAgICAgICAgICAgICAgIEZVTj1zdnltZWFuLA0KICAgICAgICAgICAgICAgICAgICAgICAgbmEucm09VCApDQoNCmhlYWQobWV0X2VzdF9lZHUpDQpoZWFkKG1ldF9lc3RfZW1wbG95KQ0KaGVhZChtZXRfZXN0X2luZHVzdHJ5KQ0KDQpgYGANCg0KDQpgYGB7cn0NCm1ldHMkbWV0MjAxMzwtYXMubnVtZXJpYyhtZXRzJEdFT0lEKQ0KZ2VvMzwtZ2VvX2pvaW4obWV0cywgbWV0X2VzdF9lbXBsb3ksYnlfc3A9ICJtZXQyMDEzIixieV9kZj0gIm1ldDIwMTMiKQ0KDQpgYGANCg0KTm90ZSwgZ3JleSBNZXRyb3MgYXJlIG9uZXMgdGhhdCBhcmUgbm90IGlkZW50aWZpZWQgaW4gdGhlIEFDUw0KYGBge3J9DQp0bWFwX21vZGUoInBsb3QiKQ0KDQp0bWFwX2xlYWZsZXQodG1fYmFzZW1hcCgiT3BlblN0cmVldE1hcC5NYXBuaWsiKSsNCiAgdG1fc2hhcGUoZ2VvMykrDQogIHRtX3BvbHlnb25zKCJlbXBsb3llZCIsDQogICAgICAgICAgICAgIHN0eWxlPSJrbWVhbnMiLA0KICAgICAgICAgICAgICBuPTgsDQogICAgICAgICAgICAgIGxlZ2VuZC5oaXN0ID0gVFJVRSkgKw0KIHRtX2xheW91dChsZWdlbmQub3V0c2lkZSA9IFRSVUUsDQogICAgICAgICAgICB0aXRsZSA9ICJFbXBsb3ltZW50IHJhdGUgaW4gVGV4YXMgTWV0cm8gQXJlYXMgXG4gMjAxNC0yMDE4IikgICkNCg0KYGBgDQoNCg0KIyMgRXN0aW1hdGlvbiBmb3IgQ291bnRpZXMNCiMjIA0KYGBge3IsIHJlc3VsdHM9J2hpZGUnfQ0KY29zPC1jb3VudGllcyhjYj0gVCxzdGF0ZSA9ICJUWCIsIHllYXIgPSAyMDE4KQ0KcGxvdChjb3NbIk5BTUUiXSkNCg0Kc3RzPC1zdGF0ZXMoY2I9VCwgeWVhcj0yMDE4KQ0Kc3RzPC1zdHMlPiUNCiAgZmlsdGVyKEdFT0lEPT00OCkNCmBgYA0KIyMgZXN0aW1hdGVzIGJ5IGNvdW50eSBhcmVhDQpgYGB7cn0NCmNvc19lc3RfZWR1PC1zdnlieShmb3JtdWxhID0gfmVkdWNfbGV2ZWwsDQogICAgICAgICAgICAgICAgICAgYnkgPSB+Y291bnR5ZmlwLA0KICAgICAgICAgICAgICAgICAgIGRlc2lnbj1zdWJzZXQoZGVzLGFnZT4yNSksDQogICAgICAgICAgICAgICAgICAgRlVOPXN2eW1lYW4sIG5hLnJtPVQgKQ0KY29zX2VzdF9lbXBsb3k8LXN2eWJ5KGZvcm11bGEgPSB+ZW1wbG95ZWQsDQogICAgICAgICAgICAgICAgICAgICAgYnkgPSB+Y291bnR5ZmlwLA0KICAgICAgICAgICAgICAgICAgICAgIGRlc2lnbj1zdWJzZXQoZGVzLCBhZ2UlaW4lMTg6NjUpLA0KICAgICAgICAgICAgICAgICAgICAgIEZVTj1zdnltZWFuLCBuYS5ybT1UICkNCmNvc19lc3RfaW5kdXN0cnk8LXN2eWJ5KGZvcm11bGEgPSB+cHJvZnRlY2gsDQogICAgICAgICAgICAgICAgICAgICAgICBieSA9IH5jb3VudHlmaXAsDQogICAgICAgICAgICAgICAgICAgICAgICBkZXNpZ249c3Vic2V0KGRlcywgZW1wbG95ZWQ9PTEpLA0KICAgICAgICAgICAgICAgICAgICAgICAgRlVOPXN2eW1lYW4sIG5hLnJtPVQgKQ0KDQpoZWFkKGNvc19lc3RfZWR1KQ0KaGVhZChjb3NfZXN0X2VtcGxveSkNCmhlYWQoY29zX2VzdF9pbmR1c3RyeSkNCg0KYGBgDQpBZ2FpbiwgdGhlIEFDUyBkb2Vzbid0IGlkZW50aWZ5IGNvdW50aWVzIGluIHRoZSBtaWNyb2RhdGEgZXhjZXB0IGZvciB0aG9zZSBjb3VudGllcyB3aXRoIHNtYWxsIHBvcHVsYXRpb25zLiBUaGUgbGlzdCBvZiBpZGVudGlmaWVkIGNvdW50aWVzIGNhbiBiZSBmb3VuZCBbaGVyZV0oaHR0cHM6Ly91c2EuaXB1bXMub3JnL3VzYS1hY3Rpb24vdmFyaWFibGVzL0NPVU5UWUZJUCNjb2Rlc19zZWN0aW9uKQ0KDQpgYGB7cn0NCmNvcyRjb2ZpcDwtYXMubnVtZXJpYyhjb3MkQ09VTlRZRlApDQoNCg0KZ2VvNDwtZ2VvX2pvaW4oY29zLCBjb3NfZXN0X2VtcGxveSxieV9zcD0gImNvZmlwIixieV9kZj0gImNvdW50eWZpcCIpDQoNCnRtYXBfbW9kZSgicGxvdCIpDQp0bWFwX2xlYWZsZXQoDQp0bV9iYXNlbWFwKCJPcGVuU3RyZWV0TWFwLk1hcG5payIpKw0KICB0bV9zaGFwZShnZW80KSsNCiAgdG1fcG9seWdvbnMoImVtcGxveWVkIiwNCiAgICAgICAgICAgICAgc3R5bGU9ImttZWFucyIsDQogICAgICAgICAgICAgIG49OCwNCiAgICAgICAgICAgICAgbGVnZW5kLmhpc3QgPSBUUlVFKSArDQogdG1fbGF5b3V0KGxlZ2VuZC5vdXRzaWRlID0gVFJVRSwNCiAgICAgICAgICAgIHRpdGxlID0gIkVtcGxveW1lbnQgcmF0ZSBpbiBUZXhhcyBDb3VudGllcyBcbiAyMDE0LTIwMTgiKSAgKQ0KDQpgYGANCg0KDQo=